
package com.yllt4cloud.ddauth.util.page;

import com.alibaba.fastjson.JSONObject;
import com.ffcs.icity.common.mybatis.Page;
import com.ffcs.icity.common.mybatis.dialect.Dialect;
import com.ffcs.icity.common.mybatis.dialect.Dialect.Type;
import com.ffcs.icity.common.mybatis.dialect.MySql5Dialect;
import com.ffcs.icity.common.mybatis.dialect.MySql5PageHepler;
import com.yllt4cloud.common.ddbase.util.JsonUtil;
import org.apache.ibatis.binding.BindingException;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Map;
import java.util.Properties;

import static org.apache.ibatis.reflection.SystemMetaObject.DEFAULT_OBJECT_FACTORY;
import static org.apache.ibatis.reflection.SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY;

@Intercepts({@Signature(
    type = StatementHandler.class,
    method = "prepare",
    args = {Connection.class, Integer.class}
)})
public class PaginationInterceptor implements Interceptor {
    private static final Logger logger = LoggerFactory.getLogger(PaginationInterceptor.class);
    static final ThreadLocal<Page> PAGE_CONTEXT = new ThreadLocal();
    public static final String PAGE_KEY = "page";

    public PaginationInterceptor() {
    }

    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        if (!(statementHandler instanceof RoutingStatementHandler)) {
            return invocation.proceed();
        } else {
            BoundSql boundSql = statementHandler.getBoundSql();
            Object parameterObject = boundSql.getParameterObject();
            Page page = this.getPage(parameterObject);
            if (null == page) {
                return invocation.proceed();
            } else {
                MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
                Configuration configuration = (Configuration) metaStatementHandler.getValue("delegate.configuration");
                String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
                page.setTotalCount(this.getTotalCount(invocation, configuration, metaStatementHandler, boundSql, originalSql));
                Type databaseType = null;

                try {
                    databaseType = Type.valueOf(configuration.getVariables().getProperty("dialect").toUpperCase());
                } catch (Exception var11) {
                }

                if (databaseType == null) {
                    throw new RuntimeException("the value of the dialect property in configuration.xml is not defined : " + configuration.getVariables().getProperty("dialect"));
                } else {
                    Dialect dialect = null;
                    switch (databaseType) {
                        case MYSQL:
                            dialect = new MySql5Dialect();
                        default:
                            metaStatementHandler.setValue("delegate.boundSql.sql", dialect.getLimitString(originalSql, (int) page.getFirst() - 1, page.getPageSize()));
                            metaStatementHandler.setValue("delegate.rowBounds.offset", 0);
                            metaStatementHandler.setValue("delegate.rowBounds.limit", 2147483647);
                            if (logger.isDebugEnabled()) {
                                logger.debug("生成分页SQL: {}", boundSql.getSql());
                            }

                            PAGE_CONTEXT.set(page);
                            return invocation.proceed();
                    }
                }
            }
        }
    }

    private Page getPage(Object parameterObject) {
        if (parameterObject instanceof Map) {
            try {
                Map paramMap = ((Map) parameterObject);
                if (paramMap.containsKey(PAGE_KEY)) {
                    Object pageValue = paramMap.get(PAGE_KEY);
                    Page page = pageValue instanceof Page ? (Page) pageValue : pageValue instanceof JSONObject ? JsonUtil.objectToObjectByJson(pageValue, Page.class) : null;
                    if (page != null)
                        paramMap.put(PAGE_KEY, page);
                    return page;
                }
            } catch (BindingException ignored) {
            }
        } else {
            try {
                if (parameterObject != null) {
                    BeanInfo beanInfo = Introspector.getBeanInfo(parameterObject.getClass());
                    for (PropertyDescriptor descriptor : beanInfo.getPropertyDescriptors()) {
                        if (descriptor.getPropertyType() == Page.class) {
                            Method method = descriptor.getReadMethod();
                            if (method != null) {
                                return (Page) method.invoke(parameterObject, new Object[]{});
                            }
                        }
                    }
                }

            } catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private long getTotalCount(Invocation invocation, Configuration configuration, MetaObject metaStatementHandler, BoundSql boundSql, String originalSql) throws Exception {
        MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
        Connection connection = (Connection) invocation.getArgs()[0];
        Object parameterObject = boundSql.getParameterObject();
        String countSql = MySql5PageHepler.getCountString(originalSql);
        logger.debug("获取总记录数的SQL: {}", countSql);
        PreparedStatement countStmt = connection.prepareStatement(countSql);
        BoundSql countBS = new BoundSql(configuration, countSql, boundSql.getParameterMappings(), parameterObject);
        MetaObject countMo = MetaObject.forObject(countBS, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
        MetaObject boundMo = MetaObject.forObject(boundSql, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
        countMo.setValue("metaParameters", boundMo.getValue("metaParameters"));
        countMo.setValue("additionalParameters", boundMo.getValue("additionalParameters"));
        (new DefaultParameterHandler(mappedStatement, parameterObject, countBS)).setParameters(countStmt);
        ResultSet rs = countStmt.executeQuery();
        int count = 0;
        if (rs.next()) {
            count = rs.getInt(1);
        }

        rs.close();
        countStmt.close();
        logger.debug("总记录数:{}", count);
        return (long) count;
    }

    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
    }
}

